﻿using System;
using System.IO;
using System.Linq;
using HashFoo.Core;

namespace HashFoo.MigratorFoo
{
    [Serializable]
    public class MigrationConfiguration
    {
        public MigrationConfiguration()
        {
            DbConfigAssemblyPath = string.Empty;
            SchemaVersionTableName = "SchemaVersion";
            UpScriptsDirectory = string.Empty;
            DownScriptsDirectory = string.Empty;
        }

        /// <summary>
        /// The path to the assembly that holds the (NHibernate) database configuration.
        /// </summary>
        public string DbConfigAssemblyPath { get; set; }

        /// <summary>
        /// The name of the table to store schema version in
        /// </summary>
        public string SchemaVersionTableName { get; set; }

        /// <summary>
        /// The directory that holds the 'up' migrations
        /// </summary>
        public string UpScriptsDirectory { get; set; }

        /// <summary>
        /// The directory that holds the 'down' migrations
        /// </summary>
        public string DownScriptsDirectory { get; set; }

        /// <summary>
        /// Use to load a configuration from file.  Use this meathod instead of direct deserialization (see remarks).
        /// </summary>
        /// <remarks>
        /// By using this meathod, relative paths are automatically resolved, and
        /// debug/build configurations are injected into paths with a {config} token (by looking to see which is newer, if no config is specified).
        /// </remarks>
        /// <param name="configPath">The path to the configuration file</param>
        /// <param name="buildConfig">The default build config name to use if no "bin" path is found</param>
        /// <returns></returns>
        public static MigrationConfiguration Load(string configPath, string buildConfig = "release")
        {
            var baseDir = new DirectoryInfo(Path.GetDirectoryName(configPath) ?? string.Empty);
            if (!baseDir.Exists) throw new IOException("configPath does not exist: " + configPath);

            var config = XmlUtil.LoadFromXml<MigrationConfiguration>(File.ReadAllText(configPath));

            // change relative paths into absolute, based on baseDir.
            config.DbConfigAssemblyPath = MakePathAbsolute(baseDir.FullName, config.DbConfigAssemblyPath);
            if (config.DbConfigAssemblyPath.ToLowerInvariant().Contains("{config}"))
            {
                config.DbConfigAssemblyPath = FindNewestBuildConfig(config.DbConfigAssemblyPath, buildConfig);
            }

            config.UpScriptsDirectory = MakePathAbsolute(baseDir.FullName, config.UpScriptsDirectory);
            config.DownScriptsDirectory = MakePathAbsolute(baseDir.FullName, config.DownScriptsDirectory);

            return config;
        }

        private static string FindNewestBuildConfig(string buildConfigPathWithToken, string defaultConfig)
        {
            defaultConfig = defaultConfig ?? "release";
            var defaultPath = buildConfigPathWithToken.Replace("{config}", defaultConfig);
            var fileName = buildConfigPathWithToken.Split('\\').LastOrDefault();
            if (fileName == null) return defaultPath;

            var parentDir = buildConfigPathWithToken.Substring(0, buildConfigPathWithToken.IndexOf("{config}"));
            if (!Directory.Exists(parentDir)) return defaultPath;

            var target = new DirectoryInfo(parentDir).GetFiles(fileName, SearchOption.AllDirectories)
                .OrderByDescending(f => f.LastWriteTimeUtc).FirstOrDefault();

            return target.FullName;
        }

        private static string MakePathAbsolute(string basePath, string pathToCheck)
        {
            return pathToCheck.StartsWith(".") ? Path.Combine(basePath, pathToCheck) : pathToCheck;
        }
    }
}